#include "gtest/gtest.h"
#include "mem/ecma_string.h"
#include "test_helper.h"

#include "plugins/ecmascript/runtime/ecma_string.h"
#include "include/coretypes/array.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/object_operator.h"
#include "include/runtime.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/tagged_dictionary.h"
#include "plugins/ecmascript/runtime/weak_vector-inl.h"
#include "plugins/ecmascript/runtime/ic/proto_change_details.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

// NOLINTBEGIN(modernize-avoid-c-arrays,readability-magic-numbers)

namespace ark::test {
class JSObjectTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {};

private:
    PandaVM *instance_ {nullptr};
    ecmascript::EcmaHandleScope *scope_ {nullptr};
};

static JSFunction *JSObjectTestCreate(JSThread *thread)
{
    JSHandle<GlobalEnv> globalEnv = thread->GetEcmaVM()->GetGlobalEnv();
    return globalEnv->GetObjectFunction().GetObject<JSFunction>();
}

TEST_F(JSObjectTest, Create)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> jsobject =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*jsobject != nullptr);
}

TEST_F(JSObjectTest, SetProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> jsobject =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*jsobject != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(jsobject), key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(jsobject), key).GetValue()->GetInt(), 1);

    JSHandle<JSTaggedValue> value2(thread_, JSTaggedValue(2));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(jsobject), key, value2);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(jsobject), key).GetValue()->GetInt(), 2);
}

TEST_F(JSObjectTest, GetProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    EXPECT_TRUE(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->IsUndefined());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt(), 1);
}

TEST_F(JSObjectTest, DeleteProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "print";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSObject::DeleteProperty(thread_, (obj), key);
    EXPECT_TRUE(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->IsUndefined());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt(), 1);

    JSHandle<JSTaggedValue> key2(thread_->GetEcmaVM()->GetFactory()->NewFromString("print_test"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2,
                          JSHandle<JSTaggedValue>(thread_, JSTaggedValue(10)));
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2).GetValue()->GetInt(), 10);

    JSObject::DeleteProperty(thread_, (obj), key);
    EXPECT_TRUE(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->IsUndefined());
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2).GetValue()->GetInt(), 10);
}

TEST_F(JSObjectTest, DeletePropertyGlobal)
{
    JSHandle<GlobalEnv> globalEnv = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSTaggedValue> global(thread_, globalEnv->GetGlobalObject());
    JSHandle<JSTaggedValue> printKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("print"));
    JSHandle<JSTaggedValue> printTestKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("print_test"));

    JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread_, global, printKey).GetValue();

    JSObject::SetProperty(thread_, global, printTestKey, value);

    JSTaggedValue val2 = JSObject::GetProperty(thread_, global, printTestKey).GetValue().GetTaggedValue();
    EXPECT_EQ(val2, value.GetTaggedValue());
    JSTaggedValue::DeletePropertyOrThrow(thread_, global, printTestKey);
    JSTaggedValue val3 = JSObject::GetProperty(thread_, global, printKey).GetValue().GetTaggedValue();
    EXPECT_NE(val3, JSTaggedValue::Undefined());
}

TEST_F(JSObjectTest, GetPropertyInPrototypeChain)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> grandfather = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> father = JSObject::ObjectCreate(thread_, grandfather);
    JSHandle<JSObject> son = JSObject::ObjectCreate(thread_, father);

    JSHandle<JSTaggedValue> sonKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key1"));
    JSHandle<JSTaggedValue> fatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key2"));
    JSHandle<JSTaggedValue> grandfatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key3"));
    JSHandle<JSTaggedValue> sonValue(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> fatherValue(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> grandfatherValue(thread_, JSTaggedValue(3));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(son), sonKey, sonValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(father), fatherKey, fatherValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(grandfather), grandfatherKey, grandfatherValue);

    EXPECT_EQ(sonValue.GetTaggedValue(),
              JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(son), sonKey).GetValue().GetTaggedValue());
    EXPECT_EQ(fatherValue.GetTaggedValue(),
              JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(son), fatherKey).GetValue().GetTaggedValue());
    EXPECT_EQ(grandfatherValue.GetTaggedValue(),
              JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(son), grandfatherKey).GetValue().GetTaggedValue());
}

TEST_F(JSObjectTest, PropertyAttribute)
{
    JSHandle<JSTaggedValue> constructor(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), constructor);
    JSHandle<JSObject> obj2 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), constructor);

    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString("key3"));
    JSHandle<JSTaggedValue> key2(thread_->GetEcmaVM()->GetFactory()->NewFromString("key3"));

    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> value2(thread_, JSTaggedValue(2));

    // test set property
    PropertyDescriptor desc(thread_);
    desc.SetValue(value1);
    desc.SetWritable(false);
    JSObject::DefineOwnProperty(thread_, obj1, key1, desc);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value2);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key1, value1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key1, value2);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key1).GetValue().GetTaggedValue(),
              value2.GetTaggedValue());

    // test delete property
    PropertyDescriptor desc1(thread_);
    desc1.SetValue(value1);
    desc1.SetConfigurable(false);
    JSObject::DefineOwnProperty(thread_, obj1, key2, desc1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key2, value1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key2, value1);
    JSObject::DeleteProperty(thread_, (obj1), key2);
    JSObject::DeleteProperty(thread_, (obj2), key2);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key2).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key2).GetValue().GetTaggedValue(),
              JSTaggedValue::Undefined());
}

TEST_F(JSObjectTest, CreateDataProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    bool success = JSObject::CreateDataProperty(thread_, obj, key, value);
    EXPECT_TRUE(success);

    success = JSTaggedValue::HasOwnProperty(thread_, JSHandle<JSTaggedValue>::Cast(obj), key);
    EXPECT_TRUE(success);

    PropertyDescriptor desc(thread_);
    success = JSObject::GetOwnProperty(thread_, obj, key, desc);
    EXPECT_TRUE(success);
    EXPECT_EQ(true, desc.IsWritable());
    EXPECT_EQ(true, desc.IsEnumerable());
    EXPECT_EQ(true, desc.IsConfigurable());
}

TEST_F(JSObjectTest, CreateMethodProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    bool success = JSObject::CreateMethodProperty(thread_, obj, key, value);
    EXPECT_TRUE(success);

    success = JSTaggedValue::HasOwnProperty(thread_, JSHandle<JSTaggedValue>::Cast(obj), key);
    EXPECT_TRUE(success);

    PropertyDescriptor desc(thread_);
    success = JSObject::GetOwnProperty(thread_, obj, key, desc);
    EXPECT_TRUE(success);
    EXPECT_EQ(true, desc.IsWritable());
    EXPECT_EQ(false, desc.IsEnumerable());
    EXPECT_EQ(true, desc.IsConfigurable());
}

TEST_F(JSObjectTest, DefinePropertyOrThrow)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));

    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), true, true, true);
    bool success = JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, desc1);
    EXPECT_TRUE(success);
    PropertyDescriptor descRes1(thread_);
    success = JSObject::GetOwnProperty(thread_, obj, key, descRes1);
    EXPECT_TRUE(success);
    EXPECT_EQ(1, descRes1.GetValue()->GetInt());
    EXPECT_EQ(true, descRes1.IsWritable());
    EXPECT_EQ(true, descRes1.IsEnumerable());
    EXPECT_EQ(true, descRes1.IsConfigurable());

    PropertyDescriptor desc2(thread_, false, true, true);
    success = JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, desc2);
    EXPECT_TRUE(success);
    PropertyDescriptor descRes2(thread_);
    success = JSObject::GetOwnProperty(thread_, obj, key, descRes2);
    EXPECT_TRUE(success);
    EXPECT_EQ(1, descRes2.GetValue()->GetInt());
    EXPECT_EQ(false, descRes2.IsWritable());
    EXPECT_EQ(true, descRes2.IsEnumerable());
    EXPECT_EQ(true, descRes2.IsConfigurable());

    PropertyDescriptor desc3(thread_);
    desc3.SetWritable(false);
    desc3.SetEnumerable(false);
    desc3.SetConfigurable(false);
    success = JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, desc3);
    EXPECT_TRUE(success);
    PropertyDescriptor descRes3(thread_);
    success = JSObject::GetOwnProperty(thread_, obj, key, descRes3);
    EXPECT_TRUE(success);
    EXPECT_EQ(1, descRes3.GetValue()->GetInt());
    EXPECT_EQ(false, descRes3.IsWritable());
    EXPECT_EQ(false, descRes3.IsEnumerable());
    EXPECT_EQ(false, descRes3.IsConfigurable());

    PropertyDescriptor desc4(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(2)));
    success = JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, desc4);
    EXPECT_FALSE(success);
}

TEST_F(JSObjectTest, HasProperty)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    char array[] = "x";
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString(array));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    bool flag = JSObject::HasProperty(thread_, obj, key);
    EXPECT_FALSE(flag);

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key, value);
    flag = JSObject::HasProperty(thread_, obj, key);
    EXPECT_TRUE(flag);

    JSObject::DeleteProperty(thread_, (obj), key);
    flag = JSObject::HasProperty(thread_, obj, key);
    EXPECT_FALSE(flag);
}

TEST_F(JSObjectTest, HasPropertyWithProtoType)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> grandfather = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> father = JSObject::ObjectCreate(thread_, grandfather);
    JSHandle<JSObject> son = JSObject::ObjectCreate(thread_, father);

    auto testGrand = grandfather->GetPrototype(thread_);
    auto testFather = father->GetPrototype(thread_);
    auto testSon = son->GetPrototype(thread_);
    EXPECT_TRUE(testSon != testFather);
    EXPECT_TRUE(testGrand != testFather);
    JSHandle<JSTaggedValue> sonKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key1"));
    JSHandle<JSTaggedValue> fatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key2"));
    JSHandle<JSTaggedValue> grandfatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key3"));
    JSHandle<JSTaggedValue> sonValue(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> fatherValue(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> grandfatherValue(thread_, JSTaggedValue(3));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(son), sonKey, sonValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(father), fatherKey, fatherValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(grandfather), grandfatherKey, grandfatherValue);

    bool flag = JSObject::HasProperty(thread_, son, sonKey);
    EXPECT_TRUE(flag);
    flag = JSObject::HasProperty(thread_, son, fatherKey);
    EXPECT_TRUE(flag);
    flag = JSObject::HasProperty(thread_, son, grandfatherKey);
    EXPECT_TRUE(flag);
}

TEST_F(JSObjectTest, HasOwnProperty)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> grandfather = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> father = JSObject::ObjectCreate(thread_, grandfather);
    JSHandle<JSObject> son = JSObject::ObjectCreate(thread_, father);

    JSHandle<JSTaggedValue> sonKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key1"));
    JSHandle<JSTaggedValue> fatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key2"));
    JSHandle<JSTaggedValue> grandfatherKey(thread_->GetEcmaVM()->GetFactory()->NewFromString("key3"));
    JSHandle<JSTaggedValue> sonValue(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> fatherValue(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> grandfatherValue(thread_, JSTaggedValue(3));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(son), sonKey, sonValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(father), fatherKey, fatherValue);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(grandfather), grandfatherKey, grandfatherValue);

    bool flag = JSTaggedValue::HasOwnProperty(thread_, JSHandle<JSTaggedValue>::Cast(son), sonKey);
    EXPECT_TRUE(flag);
    flag = JSTaggedValue::HasOwnProperty(thread_, JSHandle<JSTaggedValue>::Cast(son), fatherKey);
    EXPECT_FALSE(flag);
    flag = JSTaggedValue::HasOwnProperty(thread_, JSHandle<JSTaggedValue>::Cast(son), grandfatherKey);
    EXPECT_FALSE(flag);
}

TEST_F(JSObjectTest, GetOwnPropertyKeys)
{
    JSHandle<JSTaggedValue> constructor(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), constructor);

    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString("x"));
    JSHandle<JSTaggedValue> key2(thread_->GetEcmaVM()->GetFactory()->NewFromString("y"));
    JSHandle<JSTaggedValue> key3(thread_->GetEcmaVM()->GetFactory()->NewFromString("3"));
    JSHandle<JSTaggedValue> key4(thread_->GetEcmaVM()->GetFactory()->NewFromString("4"));
    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> value2(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> value3(thread_, JSTaggedValue(3));
    JSHandle<JSTaggedValue> value4(thread_, JSTaggedValue(4));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key1, value1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2, value2);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key3, value3);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key4, value4);

    JSHandle<TaggedArray> array = JSObject::GetOwnPropertyKeys(thread_, obj);
    int length = array->GetLength();
    EXPECT_EQ(length, 4);
    int sum = 0;
    for (int i = 0; i < length; i++) {
        JSHandle<JSTaggedValue> key(thread_, array->Get(i));
        sum += JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt();
    }
    EXPECT_EQ(sum, 10);
}

TEST_F(JSObjectTest, ObjectCreateMethod)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> grandfather = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> father = JSObject::ObjectCreate(thread_, grandfather);
    JSHandle<JSObject> son = JSObject::ObjectCreate(thread_, father);

    EXPECT_EQ(son->GetPrototype(thread_), father.GetTaggedValue());
    EXPECT_EQ(father->GetPrototype(thread_), grandfather.GetTaggedValue());
    EXPECT_EQ(grandfather->GetPrototype(thread_), JSTaggedValue::Null());
}

TEST_F(JSObjectTest, GetMethod)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSTaggedValue> func(thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env));
    EXPECT_TRUE(*func != nullptr);
    JSHandle<JSTaggedValue> key(thread_->GetEcmaVM()->GetFactory()->NewFromString("1"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key, func);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue().GetTaggedValue(),
              func.GetTaggedValue());
}

TEST_F(JSObjectTest, EnumerableOwnNames)
{
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(*obj != nullptr);

    PandaString tagCStr = "x";
    JSHandle<EcmaString> tagString = thread_->GetEcmaVM()->GetFactory()->NewFromString(&tagCStr[0]);
    JSHandle<JSTaggedValue> key(tagString);

    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key, value);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt(), 1);

    JSHandle<TaggedArray> names = JSObject::EnumerableOwnNames(thread_, obj);

    JSHandle<JSTaggedValue> keyFromNames(thread_, JSTaggedValue(names->Get(0)));
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), keyFromNames).GetValue()->GetInt(), 1);

    PropertyDescriptor descNoEnum(thread_);
    descNoEnum.SetEnumerable(false);
    JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, descNoEnum);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt(), 1);

    JSHandle<TaggedArray> namesNoEnum = JSObject::EnumerableOwnNames(thread_, obj);
    EXPECT_TRUE(namesNoEnum->GetLength() == 0);

    PropertyDescriptor descEnum(thread_);
    descEnum.SetConfigurable(false);
    descEnum.SetEnumerable(true);
    JSTaggedValue::DefinePropertyOrThrow(thread_, JSHandle<JSTaggedValue>(obj), key, descEnum);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue()->GetInt(), 1);

    JSHandle<TaggedArray> namesNoConfig = JSObject::EnumerableOwnNames(thread_, obj);

    JSHandle<JSTaggedValue> keyNoConfig(thread_, JSTaggedValue(namesNoConfig->Get(0)));
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), keyNoConfig).GetValue()->GetInt(), 1);
}

TEST_F(JSObjectTest, SetIntegrityLevelSealed)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    EXPECT_TRUE(*obj1 != nullptr);
    PandaString undefinedCStr = "x";
    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString(&undefinedCStr[0]));
    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value1);

    // test SetIntegrityLevel::SEALED
    JSHandle<JSObject> jsobject(obj1);
    bool status1 = JSObject::SetIntegrityLevel(thread_, jsobject, IntegrityLevel::SEALED);
    EXPECT_TRUE(status1);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());
    PropertyDescriptor desc1(thread_);
    bool success1 = JSObject::GetOwnProperty(thread_, jsobject, key1, desc1);
    EXPECT_TRUE(success1);
    EXPECT_EQ(true, desc1.IsWritable());
    EXPECT_EQ(true, desc1.IsEnumerable());
    EXPECT_EQ(false, desc1.IsConfigurable());
}

TEST_F(JSObjectTest, SetIntegrityLevelFrozen)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    EXPECT_TRUE(*obj1 != nullptr);

    PandaString undefinedCStr = "x";
    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString(&undefinedCStr[0]));
    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value1);

    // test SetIntegrityLevel::FROZEN
    bool status1 = JSObject::SetIntegrityLevel(thread_, obj1, IntegrityLevel::FROZEN);
    EXPECT_TRUE(status1);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());
    PropertyDescriptor desc1(thread_);
    bool success1 = JSObject::GetOwnProperty(thread_, obj1, key1, desc1);
    EXPECT_TRUE(success1);
    EXPECT_EQ(false, desc1.IsWritable());
    EXPECT_EQ(true, desc1.IsEnumerable());
    EXPECT_EQ(false, desc1.IsConfigurable());
}

TEST_F(JSObjectTest, TestIntegrityLevelSealed)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    PandaString undefinedCStr = "level";
    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString(&undefinedCStr[0]));
    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value1);
    obj1->GetJSHClass()->SetExtensible(false);

    // test SetIntegrityLevel::SEALED
    bool status1 = JSObject::SetIntegrityLevel(thread_, obj1, IntegrityLevel::SEALED);
    EXPECT_TRUE(status1);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());

    PropertyDescriptor desc1(thread_);
    bool success1 = JSObject::GetOwnProperty(thread_, obj1, key1, desc1);
    EXPECT_TRUE(success1);
    EXPECT_EQ(true, JSObject::TestIntegrityLevel(thread_, obj1, IntegrityLevel::SEALED));
    EXPECT_EQ(false, JSObject::TestIntegrityLevel(thread_, obj1, IntegrityLevel::FROZEN));
}

TEST_F(JSObjectTest, TestIntegrityLevelFrozen)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 =
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    PandaString undefinedCStr = "level";
    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString(&undefinedCStr[0]));
    JSHandle<JSTaggedValue> value1(thread_, JSTaggedValue(1));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value1);
    obj1->GetJSHClass()->SetExtensible(false);

    // test SetIntegrityLevel::FROZEN
    bool status1 = JSObject::SetIntegrityLevel(thread_, obj1, IntegrityLevel::FROZEN);
    EXPECT_TRUE(status1);
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1).GetValue().GetTaggedValue(),
              value1.GetTaggedValue());

    PropertyDescriptor desc1(thread_);
    bool success1 = JSObject::GetOwnProperty(thread_, obj1, key1, desc1);
    EXPECT_TRUE(success1);
    EXPECT_EQ(true, JSObject::TestIntegrityLevel(thread_, obj1, IntegrityLevel::SEALED));
    EXPECT_EQ(true, JSObject::TestIntegrityLevel(thread_, obj1, IntegrityLevel::FROZEN));
}

TEST_F(JSObjectTest, TestIntegrityLevelWithoutProperty)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> obj1(
        thread_->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1));
    JSHandle<JSObject>::Cast(obj1)->GetJSHClass()->SetExtensible(false);
    PandaString undefinedCStr = "level";
    JSHandle<JSTaggedValue> key1(thread_->GetEcmaVM()->GetFactory()->NewFromString(&undefinedCStr[0]));

    // test SetIntegrityLevel::FROZEN
    JSHandle<JSObject> jsobject(obj1);
    bool status1 = JSObject::SetIntegrityLevel(thread_, jsobject, IntegrityLevel::SEALED);
    EXPECT_TRUE(status1);

    PropertyDescriptor desc1(thread_);
    bool success1 = JSObject::GetOwnProperty(thread_, jsobject, key1, desc1);
    EXPECT_TRUE(!success1);
    EXPECT_EQ(true, JSObject::TestIntegrityLevel(thread_, jsobject, IntegrityLevel::SEALED));
    EXPECT_EQ(true, JSObject::TestIntegrityLevel(thread_, jsobject, IntegrityLevel::FROZEN));
}

JSTaggedValue TestGetter(EcmaRuntimeCallInfo *argv)
{
    auto thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj(builtins_common::GetThis(argv));
    JSHandle<JSTaggedValue> key(factory->NewFromString("y"));
    JSTaggedValue value = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>(obj), key).GetValue().GetTaggedValue();

    return JSTaggedValue(value.GetInt() + 1);
}

TEST_F(JSObjectTest, Getter)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    JSHandle<JSTaggedValue> key1(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> key2(factory->NewFromString("y"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> getter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestGetter));

    PropertyDescriptor desc1(thread_);
    desc1.SetGetter(JSHandle<JSTaggedValue>::Cast(getter));
    bool success1 = JSObject::DefineOwnProperty(thread_, obj, key1, desc1);
    EXPECT_TRUE(success1);

    PropertyDescriptor desc2(thread_);
    desc2.SetValue(JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)));
    success1 = JSObject::DefineOwnProperty(thread_, obj, key2, desc2);
    EXPECT_TRUE(success1);

    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key1).GetValue().GetTaggedValue(),
              JSTaggedValue(2));
}

JSTaggedValue TestSetter(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj(builtins_common::GetThis(argv));
    JSHandle<JSTaggedValue> key(factory->NewFromString("y"));
    JSTaggedValue value(JSObject::GetProperty(thread, JSHandle<JSTaggedValue>(obj), key).GetValue().GetTaggedValue());
    JSHandle<JSTaggedValue> valueHandle(thread, JSTaggedValue(value.GetInt() + 1));
    JSObject::SetProperty(thread, JSHandle<JSTaggedValue>(obj), key, valueHandle);

    return JSTaggedValue(JSTaggedValue::True());
}

TEST_F(JSObjectTest, Setter)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    JSHandle<JSTaggedValue> key1(factory->NewFromString("x"));
    JSHandle<JSTaggedValue> key2(factory->NewFromString("y"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> setter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestSetter));

    PropertyDescriptor desc1(thread_);
    desc1.SetSetter(JSHandle<JSTaggedValue>::Cast(setter));
    bool success1 = JSObject::DefineOwnProperty(thread_, obj, key1, desc1);
    EXPECT_TRUE(success1);

    PropertyDescriptor desc2(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), true, true, true);
    success1 = JSObject::DefineOwnProperty(thread_, obj, key2, desc2);
    EXPECT_TRUE(success1);

    JSHandle<JSTaggedValue> valueHandle(thread_, JSTaggedValue::Undefined());
    EXPECT_TRUE(JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key1, valueHandle));
    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2).GetValue().GetTaggedValue(),
              JSTaggedValue(2));
}

TEST_F(JSObjectTest, SpeciesConstructor)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    const GlobalEnvConstants *globalConst = thread_->GlobalConstants();
    JSHandle<JSFunction> constructorFunc =
        factory->NewJSFunction(env, static_cast<void *>(nullptr), FunctionKind::BASE_CONSTRUCTOR);
    JSHandle<JSTaggedValue> constructorFuncValue(constructorFunc);
    constructorFunc->GetJSHClass()->SetExtensible(true);
    JSFunction::NewJSFunctionPrototype(thread_, factory, constructorFunc);

    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSTaggedValue> undefinedValue(thread_, JSTaggedValue::Undefined());
    JSHandle<JSObject> protoObj = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSTaggedValue> protoObjValue(protoObj);

    JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString();
    JSObject::SetProperty(thread_, protoObjValue, constructorKey, constructorFuncValue);

    factory->NewJSObjectByConstructor(constructorFunc, JSHandle<JSTaggedValue>::Cast(constructorFunc));
    JSHandle<JSFunction> speciesConstruct =
        factory->NewJSFunction(env, static_cast<void *>(nullptr), FunctionKind::BASE_CONSTRUCTOR);
    JSHandle<JSTaggedValue> speciesConstructValue(speciesConstruct);
    constructorFunc->GetJSHClass()->SetExtensible(true);
    JSFunction::MakeConstructor(thread_, speciesConstruct, undefinedValue);

    JSHandle<JSTaggedValue> speciesSymbol = env->GetSpeciesSymbol();
    JSObject::SetProperty(thread_, constructorFuncValue, speciesSymbol, speciesConstructValue);

    JSTaggedValue speciesValue = JSObject::SpeciesConstructor(thread_, protoObj, constructorFuncValue).GetTaggedValue();
    EXPECT_EQ(speciesValue, speciesConstructValue.GetTaggedValue());
}

JSTaggedValue TestUndefinedGetter([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(10);
}

JSTaggedValue TestUndefinedSetter([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    return JSTaggedValue(10);
}

TEST_F(JSObjectTest, GetterIsUndefined)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    JSHandle<JSTaggedValue> key(factory->NewFromString("property"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> getter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestUndefinedGetter));
    JSHandle<JSFunction> setter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestUndefinedSetter));
    JSHandle<JSTaggedValue> unGetter(thread_, JSTaggedValue::Undefined());

    PropertyDescriptor desc1(thread_);
    desc1.SetGetter(JSHandle<JSTaggedValue>::Cast(getter));
    desc1.SetSetter(JSHandle<JSTaggedValue>::Cast(setter));
    desc1.SetConfigurable(true);
    desc1.SetEnumerable(true);
    bool success1 = JSObject::DefineOwnProperty(thread_, obj, key, desc1);
    EXPECT_TRUE(success1);

    PropertyDescriptor desc2(thread_);
    desc2.SetGetter(unGetter);
    bool success2 = JSObject::DefineOwnProperty(thread_, obj, key, desc2);
    EXPECT_TRUE(success2);

    PropertyDescriptor desc(thread_);
    bool success = JSObject::GetOwnProperty(thread_, obj, key, desc);
    EXPECT_TRUE(success);
    EXPECT_TRUE(desc.GetSetter()->IsJSFunction());
    EXPECT_TRUE(desc.GetGetter()->IsUndefined());
}

TEST_F(JSObjectTest, SetterIsUndefined)
{
    JSHandle<JSTaggedValue> dynclass1(thread_, JSObjectTestCreate(thread_));
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass1), dynclass1);
    JSHandle<JSTaggedValue> key(factory->NewFromString("property"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> getter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestUndefinedGetter));
    JSHandle<JSFunction> setter =
        thread_->GetEcmaVM()->GetFactory()->NewJSFunction(env, reinterpret_cast<void *>(TestUndefinedSetter));
    JSHandle<JSTaggedValue> unSetter(thread_, JSTaggedValue::Undefined());

    PropertyDescriptor desc1(thread_);
    desc1.SetGetter(JSHandle<JSTaggedValue>::Cast(getter));
    desc1.SetSetter(JSHandle<JSTaggedValue>::Cast(setter));
    desc1.SetConfigurable(true);
    desc1.SetEnumerable(true);
    bool success1 = JSObject::DefineOwnProperty(thread_, obj, key, desc1);
    EXPECT_TRUE(success1);

    PropertyDescriptor desc2(thread_);
    desc2.SetSetter(unSetter);
    bool success2 = JSObject::DefineOwnProperty(thread_, obj, key, desc2);
    EXPECT_TRUE(success2);

    PropertyDescriptor desc(thread_);
    bool success = JSObject::GetOwnProperty(thread_, obj, key, desc);
    EXPECT_TRUE(success);
    EXPECT_TRUE(desc.GetSetter()->IsUndefined());

    EXPECT_EQ(JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), key).GetValue().GetTaggedValue(),
              JSTaggedValue(10));
}

TEST_F(JSObjectTest, HClass)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    JSHandle<JSHClass> hc0(thread_, obj1->GetJSHClass());

    JSHandle<JSTaggedValue> key1(factory->NewFromCanBeCompressString("x"));
    JSHandle<JSTaggedValue> key2(factory->NewFromCanBeCompressString("y"));
    JSHandle<JSTaggedValue> key3(factory->NewFromCanBeCompressString("z"));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, value);
    JSHandle<JSHClass> hc1(thread_, obj1->GetJSHClass());
    EXPECT_NE(hc0.GetTaggedValue(), hc1.GetTaggedValue());
    EXPECT_EQ(hc0.GetTaggedValue(), hc1->GetParent());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key2, value);
    JSHandle<JSHClass> hc2(thread_, obj1->GetJSHClass());
    EXPECT_NE(hc1.GetTaggedValue(), hc2.GetTaggedValue());
    EXPECT_EQ(hc1.GetTaggedValue(), hc2->GetParent());

    JSHandle<JSObject> obj2 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_EQ(hc0.GetTaggedValue().GetTaggedObject(), obj2->GetJSHClass());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key1, value);
    EXPECT_EQ(hc1.GetTaggedValue().GetTaggedObject(), obj2->GetJSHClass());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key3, value);
    JSHandle<JSHClass> hc3(thread_, obj2->GetJSHClass());
    EXPECT_NE(hc1.GetTaggedValue().GetTaggedObject(), obj2->GetJSHClass());
    EXPECT_EQ(hc1.GetTaggedValue(), obj2->GetJSHClass()->GetParent());

    JSHandle<JSObject> obj3 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_EQ(hc0.GetTaggedValue().GetTaggedObject(), obj3->GetJSHClass());
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key1, value);
    EXPECT_EQ(hc1.GetTaggedValue().GetTaggedObject(), obj3->GetJSHClass());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key2, value);
    EXPECT_EQ(hc2.GetTaggedValue().GetTaggedObject(), obj3->GetJSHClass());

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key3, value);
    EXPECT_NE(hc3.GetTaggedValue().GetTaggedObject(), obj3->GetJSHClass());
    EXPECT_EQ(hc2.GetTaggedValue(), obj3->GetJSHClass()->GetParent());
}

TEST_F(JSObjectTest, FastToSlow)
{
    auto ecmaVm = thread_->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);

    JSMutableHandle<EcmaString> key(factory->NewFromCanBeCompressString("x"));
    JSMutableHandle<JSTaggedValue> number(thread_, JSTaggedValue(0));
    JSMutableHandle<JSTaggedValue> newkey(thread_, JSTaggedValue(0));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    for (uint32_t i = 0; i < PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES; i++) {
        number.Update(JSTaggedValue(i));
        number.Update(JSTaggedValue::ToString(thread_, number).GetTaggedValue());
        EcmaString *newString = *factory->ConcatFromString(key, JSTaggedValue::ToString(thread_, number));
        newkey.Update(JSTaggedValue(newString));
        JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), newkey, value);
    }

    EXPECT_FALSE(TaggedArray::Cast(obj1->GetProperties().GetTaggedObject())->IsDictionaryMode());

    number.Update(JSTaggedValue(PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES));
    number.Update(JSTaggedValue::ToString(thread_, number).GetTaggedValue());
    EcmaString *newString = *factory->ConcatFromString(key, JSTaggedValue::ToString(thread_, number));
    newkey.Update(JSTaggedValue(newString));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), newkey, value);

    EXPECT_TRUE(TaggedArray::Cast(obj1->GetProperties().GetTaggedObject())->IsDictionaryMode());
    NameDictionary *dict = NameDictionary::Cast(obj1->GetProperties().GetTaggedObject());
    EXPECT_EQ(dict->EntriesCount(), PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES + 1);
    EXPECT_EQ(dict->NextEnumerationIndex(thread_), PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES + 1);
}

TEST_F(JSObjectTest, DeleteMiddle)
{
    auto ecmaVm = thread_->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj1 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);

    JSMutableHandle<EcmaString> key(factory->NewFromCanBeCompressString("x"));
    JSMutableHandle<JSTaggedValue> number(thread_, JSTaggedValue(0));
    JSMutableHandle<JSTaggedValue> newkey(thread_, JSTaggedValue(0));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(1));

    for (uint32_t i = 0; i < 10; i++) {
        number.Update(JSTaggedValue(i));
        number.Update(JSTaggedValue::ToString(thread_, number).GetTaggedValue());
        EcmaString *newString = *factory->ConcatFromString(key, JSTaggedValue::ToString(thread_, number));
        newkey.Update(JSTaggedValue(newString));
        JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), newkey, value);
    }

    EXPECT_FALSE(TaggedArray::Cast(obj1->GetProperties().GetTaggedObject())->IsDictionaryMode());

    JSMutableHandle<JSTaggedValue> key5(factory->NewFromCanBeCompressString("x5"));
    JSObject::DeleteProperty(thread_, (obj1), key5);

    EXPECT_TRUE(TaggedArray::Cast(obj1->GetProperties().GetTaggedObject())->IsDictionaryMode());
    NameDictionary *dict = NameDictionary::Cast(obj1->GetProperties().GetTaggedObject());
    EXPECT_EQ(dict->EntriesCount(), 9);
    EXPECT_FALSE(JSObject::HasProperty(thread_, obj1, key5));
}

TEST_F(JSObjectTest, ElementFastToSlow)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSTaggedValue> key0(thread_, JSTaggedValue(0));
    JSHandle<JSTaggedValue> key1(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> key2(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> key2000(thread_, JSTaggedValue(2000));
    JSHandle<JSTaggedValue> keyStr(factory->NewFromCanBeCompressString("str"));

    // test dictionary [0,1,2,...,2000]
    JSHandle<JSObject> obj1 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    EXPECT_TRUE(!TaggedArray::Cast(obj1->GetElements().GetTaggedObject())->IsDictionaryMode());
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), keyStr, key2);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key0, key0);
    EXPECT_TRUE(!TaggedArray::Cast(obj1->GetElements().GetTaggedObject())->IsDictionaryMode());
    JSHandle<JSHClass> dynClass(thread_, obj1->GetJSHClass());
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key1, key1);
    EXPECT_TRUE(!TaggedArray::Cast(obj1->GetElements().GetTaggedObject())->IsDictionaryMode());
    EXPECT_EQ(obj1->GetJSHClass(), *dynClass);

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), key2000, key2000);
    EXPECT_TRUE(TaggedArray::Cast(obj1->GetElements().GetTaggedObject())->IsDictionaryMode());
    JSTaggedValue value =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj1), keyStr).GetValue().GetTaggedValue();
    EXPECT_EQ(value, key2.GetTaggedValue());
    // test holey [0,,2]
    JSHandle<JSObject> obj2 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key0, key0);
    EXPECT_TRUE(!TaggedArray::Cast(obj2->GetElements().GetTaggedObject())->IsDictionaryMode());
    JSHandle<JSHClass> dynClass2(thread_, obj2->GetJSHClass());
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), key2, key2);
    EXPECT_TRUE(!TaggedArray::Cast(obj2->GetElements().GetTaggedObject())->IsDictionaryMode());
    EXPECT_EQ(obj2->GetJSHClass(), *dynClass2);
    // test change attr
    JSHandle<JSObject> obj3 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key0, key0);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key1, key1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), key2, key2);
    EXPECT_TRUE(!TaggedArray::Cast(obj3->GetElements().GetTaggedObject())->IsDictionaryMode());
    PropertyDescriptor desc(thread_);
    desc.SetValue(key1);
    desc.SetWritable(false);
    JSObject::DefineOwnProperty(thread_, obj3, key1, desc);
    EXPECT_TRUE(TaggedArray::Cast(obj3->GetElements().GetTaggedObject())->IsDictionaryMode());
    // test delete element
    JSHandle<JSObject> obj4 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), key0, key0);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), key1, key1);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), key2, key2);
    EXPECT_TRUE(!TaggedArray::Cast(obj4->GetElements().GetTaggedObject())->IsDictionaryMode());
    JSObject::DeleteProperty(thread_, (obj4), key1);
    EXPECT_TRUE(TaggedArray::Cast(obj4->GetElements().GetTaggedObject())->IsDictionaryMode());

    JSHandle<JSTaggedValue> value1001(thread_, JSTaggedValue(1001));
    JSHandle<JSObject> obj100 = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    PropertyDescriptor desc1(thread_);
    desc1.SetValue(value1001);
    desc1.SetWritable(false);
    desc1.SetEnumerable(false);
    desc1.SetConfigurable(false);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj100), key0, key1);
    JSObject::DefineOwnProperty(thread_, obj100, key0, desc1);
    JSTaggedValue result1001 =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(obj100), key0).GetValue().GetTaggedValue();
    EXPECT_EQ(result1001, value1001.GetTaggedValue());
}

TEST_F(JSObjectTest, EnableProtoChangeMarker)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj1 = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> obj2 = JSObject::ObjectCreate(thread_, obj1);
    JSHandle<JSObject> obj3 = JSObject::ObjectCreate(thread_, obj2);

    JSHandle<JSTaggedValue> obj1Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key1"));
    JSHandle<JSTaggedValue> obj2Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key2"));
    JSHandle<JSTaggedValue> obj3Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key3"));
    JSHandle<JSTaggedValue> obj1Value(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> obj2Value(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> obj3Value(thread_, JSTaggedValue(3));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), obj1Key, obj1Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), obj2Key, obj2Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), obj3Key, obj3Value);
    JSHandle<JSHClass> obj3Dynclass(thread_, obj3->GetJSHClass());
    JSHandle<JSTaggedValue> resultMarker = JSHClass::EnableProtoChangeMarker(thread_, obj3Dynclass);
    EXPECT_TRUE(resultMarker->IsProtoChangeMarker());
    bool hasChanged = ProtoChangeMarker::Cast(resultMarker->GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(!hasChanged);

    JSHandle<JSHClass> obj1Dynclass(thread_, obj1->GetJSHClass());
    JSHandle<JSHClass> obj2Dynclass(thread_, obj2->GetJSHClass());
    JSTaggedValue obj2Marker = obj2Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(obj2Marker.IsProtoChangeMarker());
    bool hasChanged2 = ProtoChangeMarker::Cast(obj2Marker.GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(!hasChanged2);

    JSTaggedValue obj1Marker = obj1Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(!obj1Marker.IsProtoChangeMarker());

    JSTaggedValue protoDetails2 = obj2Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails2.IsProtoChangeDetails());
    JSTaggedValue protoDetails1 = obj1Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails1.IsProtoChangeDetails());
    JSTaggedValue listeners1 = ProtoChangeDetails::Cast(protoDetails1.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners1 != JSTaggedValue(0));
    JSTaggedValue listeners2 = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners2 == JSTaggedValue(0));
    JSTaggedValue index = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetRegisterIndex();
    JSTaggedValue listenersResult = ChangeListener::Cast(listeners1.GetTaggedObject())->Get(index.GetArrayLength());
    EXPECT_TRUE(listenersResult == obj2Dynclass.GetTaggedValue());
}

TEST_F(JSObjectTest, BuildRegisterTree)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj1 = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> obj2 = JSObject::ObjectCreate(thread_, obj1);
    JSHandle<JSObject> obj3 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj4 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj5 = JSObject::ObjectCreate(thread_, obj4);
    JSHandle<JSObject> obj6 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj7 = JSObject::ObjectCreate(thread_, obj6);

    JSHandle<JSTaggedValue> obj1Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key1"));
    JSHandle<JSTaggedValue> obj2Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key2"));
    JSHandle<JSTaggedValue> obj3Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key3"));
    JSHandle<JSTaggedValue> obj4Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key4"));
    JSHandle<JSTaggedValue> obj5Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key5"));
    JSHandle<JSTaggedValue> obj6Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key6"));
    JSHandle<JSTaggedValue> obj7Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key7"));

    JSHandle<JSTaggedValue> obj1Value(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> obj2Value(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> obj3Value(thread_, JSTaggedValue(3));
    JSHandle<JSTaggedValue> obj4Value(thread_, JSTaggedValue(4));
    JSHandle<JSTaggedValue> obj5Value(thread_, JSTaggedValue(5));
    JSHandle<JSTaggedValue> obj6Value(thread_, JSTaggedValue(6));
    JSHandle<JSTaggedValue> obj7Value(thread_, JSTaggedValue(7));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), obj1Key, obj1Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), obj2Key, obj2Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), obj3Key, obj3Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), obj4Key, obj4Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj5), obj5Key, obj5Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj6), obj6Key, obj6Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj7), obj7Key, obj7Value);

    JSHandle<JSHClass> obj1Dynclass(thread_, obj1->GetJSHClass());
    JSHandle<JSHClass> obj2Dynclass(thread_, obj2->GetJSHClass());
    JSHandle<JSHClass> obj3Dynclass(thread_, obj3->GetJSHClass());
    JSHandle<JSHClass> obj4Dynclass(thread_, obj4->GetJSHClass());
    JSHandle<JSHClass> obj5Dynclass(thread_, obj5->GetJSHClass());
    JSHandle<JSHClass> obj6Dynclass(thread_, obj6->GetJSHClass());
    JSHandle<JSHClass> obj7Dynclass(thread_, obj7->GetJSHClass());

    JSHandle<JSTaggedValue> result3Marker = JSHClass::EnableProtoChangeMarker(thread_, obj3Dynclass);
    JSHandle<JSTaggedValue> result5Marker = JSHClass::EnableProtoChangeMarker(thread_, obj5Dynclass);
    EXPECT_TRUE(result3Marker->IsProtoChangeMarker());
    EXPECT_TRUE(!(ProtoChangeMarker::Cast(result3Marker->GetTaggedObject())->GetHasChanged()));
    EXPECT_TRUE(result5Marker->IsProtoChangeMarker());
    EXPECT_TRUE(!(ProtoChangeMarker::Cast(result5Marker->GetTaggedObject())->GetHasChanged()));

    EXPECT_TRUE(obj4Dynclass->GetProtoChangeMarker().IsProtoChangeMarker());
    EXPECT_TRUE(!obj6Dynclass->GetProtoChangeMarker().IsProtoChangeMarker());

    JSHandle<JSTaggedValue> result7Marker = JSHClass::EnableProtoChangeMarker(thread_, obj7Dynclass);
    EXPECT_TRUE(result7Marker->IsProtoChangeMarker());
    EXPECT_TRUE(!(ProtoChangeMarker::Cast(result7Marker->GetTaggedObject())->GetHasChanged()));

    JSTaggedValue protoDetails1 = obj1Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails1.IsProtoChangeDetails());
    JSTaggedValue listeners1Value = ProtoChangeDetails::Cast(protoDetails1.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners1Value != JSTaggedValue(0));
    JSHandle<ChangeListener> listeners1(thread_, listeners1Value.GetTaggedObject());
    JSTaggedValue protoDetails2 = obj2Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails2.IsProtoChangeDetails());
    JSTaggedValue index2 = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetRegisterIndex();
    EXPECT_TRUE(listeners1->Get(index2.GetArrayLength()) == obj2Dynclass.GetTaggedValue());

    JSTaggedValue listeners2Value = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners2Value != JSTaggedValue(0));
    JSHandle<ChangeListener> listeners2(thread_, listeners2Value.GetTaggedObject());
    JSTaggedValue protoDetails4 = obj4Dynclass->GetProtoChangeDetails();
    JSTaggedValue protoDetails6 = obj6Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails4.IsProtoChangeDetails());
    EXPECT_TRUE(protoDetails6.IsProtoChangeDetails());
    JSTaggedValue index4 = ProtoChangeDetails::Cast(protoDetails4.GetTaggedObject())->GetRegisterIndex();
    EXPECT_TRUE(listeners2->Get(index4.GetArrayLength()) == obj4Dynclass.GetTaggedValue());
    JSTaggedValue index6 = ProtoChangeDetails::Cast(protoDetails6.GetTaggedObject())->GetRegisterIndex();
    EXPECT_TRUE(listeners2->Get(index6.GetArrayLength()) == obj6Dynclass.GetTaggedValue());

    EXPECT_TRUE(listeners1->GetEnd() == 1);
    EXPECT_TRUE(listeners2->GetEnd() == 2);
}

TEST_F(JSObjectTest, NoticeThroughChain)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj1 = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> obj2 = JSObject::ObjectCreate(thread_, obj1);
    JSHandle<JSObject> obj3 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj4 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj5 = JSObject::ObjectCreate(thread_, obj4);
    JSHandle<JSObject> obj6 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj7 = JSObject::ObjectCreate(thread_, obj6);

    JSHandle<JSTaggedValue> obj1Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key1"));
    JSHandle<JSTaggedValue> obj2Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key2"));
    JSHandle<JSTaggedValue> obj3Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key3"));
    JSHandle<JSTaggedValue> obj4Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key4"));
    JSHandle<JSTaggedValue> obj5Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key5"));
    JSHandle<JSTaggedValue> obj6Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key6"));
    JSHandle<JSTaggedValue> obj7Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key7"));

    JSHandle<JSTaggedValue> obj1Value(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> obj2Value(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> obj3Value(thread_, JSTaggedValue(3));
    JSHandle<JSTaggedValue> obj4Value(thread_, JSTaggedValue(4));
    JSHandle<JSTaggedValue> obj5Value(thread_, JSTaggedValue(5));
    JSHandle<JSTaggedValue> obj6Value(thread_, JSTaggedValue(6));
    JSHandle<JSTaggedValue> obj7Value(thread_, JSTaggedValue(7));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), obj1Key, obj1Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), obj2Key, obj2Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), obj3Key, obj3Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), obj4Key, obj4Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj5), obj5Key, obj5Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj6), obj6Key, obj6Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj7), obj7Key, obj7Value);

    JSHandle<JSHClass> obj1Dynclass(thread_, obj1->GetJSHClass());
    JSHandle<JSHClass> obj2Dynclass(thread_, obj2->GetJSHClass());
    JSHandle<JSHClass> obj3Dynclass(thread_, obj3->GetJSHClass());
    JSHandle<JSHClass> obj4Dynclass(thread_, obj4->GetJSHClass());
    JSHandle<JSHClass> obj5Dynclass(thread_, obj5->GetJSHClass());
    JSHandle<JSHClass> obj6Dynclass(thread_, obj6->GetJSHClass());
    JSHandle<JSHClass> obj7Dynclass(thread_, obj7->GetJSHClass());

    JSHClass::EnableProtoChangeMarker(thread_, obj3Dynclass);
    JSHClass::EnableProtoChangeMarker(thread_, obj7Dynclass);
    JSHClass::EnableProtoChangeMarker(thread_, obj5Dynclass);

    JSHClass::NoticeThroughChain(thread_, obj2Dynclass);
    JSHClass::UnregisterOnProtoChain(thread_, obj2Dynclass);
    JSTaggedValue protoDetails1 = obj1Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails1.IsProtoChangeDetails());
    JSTaggedValue listeners1Value = ProtoChangeDetails::Cast(protoDetails1.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners1Value != JSTaggedValue(0));
    JSHandle<ChangeListener> listeners1(thread_, listeners1Value.GetTaggedObject());
    uint32_t holeIndex = ChangeListener::CheckHole(listeners1);
    EXPECT_TRUE(holeIndex == 0);

    JSTaggedValue protoDetails2 = obj2Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails2.IsProtoChangeDetails());
    JSTaggedValue listeners2Value = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners2Value != JSTaggedValue(0));
    JSTaggedValue index2 = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetRegisterIndex();
    EXPECT_TRUE(listeners1->Get(index2.GetArrayLength()) == JSTaggedValue::Hole());

    JSTaggedValue obj6Marker = obj6Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(obj6Marker.IsProtoChangeMarker());
    bool hasChanged6 = ProtoChangeMarker::Cast(obj6Marker.GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(hasChanged6);

    JSTaggedValue obj4Marker = obj4Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(obj4Marker.IsProtoChangeMarker());
    bool hasChanged4 = ProtoChangeMarker::Cast(obj4Marker.GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(hasChanged4);
}

TEST_F(JSObjectTest, ChangeProtoAndNoticeTheChain)
{
    JSHandle<JSObject> nullHandle(thread_, JSTaggedValue::Null());
    JSHandle<JSObject> obj1 = JSObject::ObjectCreate(thread_, nullHandle);
    JSHandle<JSObject> obj2 = JSObject::ObjectCreate(thread_, obj1);
    JSHandle<JSObject> obj3 = JSObject::ObjectCreate(thread_, obj1);
    JSHandle<JSObject> obj4 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj5 = JSObject::ObjectCreate(thread_, obj4);
    JSHandle<JSObject> obj6 = JSObject::ObjectCreate(thread_, obj2);
    JSHandle<JSObject> obj7 = JSObject::ObjectCreate(thread_, obj6);

    JSHandle<JSTaggedValue> obj1Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key1"));
    JSHandle<JSTaggedValue> obj2Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key2"));
    JSHandle<JSTaggedValue> obj3Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key3"));
    JSHandle<JSTaggedValue> obj4Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key4"));
    JSHandle<JSTaggedValue> obj5Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key5"));
    JSHandle<JSTaggedValue> obj6Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key6"));
    JSHandle<JSTaggedValue> obj7Key(thread_->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("key7"));

    JSHandle<JSTaggedValue> obj1Value(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> obj2Value(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> obj3Value(thread_, JSTaggedValue(3));
    JSHandle<JSTaggedValue> obj4Value(thread_, JSTaggedValue(4));
    JSHandle<JSTaggedValue> obj5Value(thread_, JSTaggedValue(5));
    JSHandle<JSTaggedValue> obj6Value(thread_, JSTaggedValue(6));
    JSHandle<JSTaggedValue> obj7Value(thread_, JSTaggedValue(7));

    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj1), obj1Key, obj1Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj2), obj2Key, obj2Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj3), obj3Key, obj3Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj4), obj4Key, obj4Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj5), obj5Key, obj5Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj6), obj6Key, obj6Value);
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj7), obj7Key, obj7Value);

    JSHandle<JSHClass> obj5Dynclass(thread_, obj5->GetJSHClass());
    JSHandle<JSHClass> obj7Dynclass(thread_, obj7->GetJSHClass());

    JSHClass::EnableProtoChangeMarker(thread_, obj7Dynclass);
    JSHClass::EnableProtoChangeMarker(thread_, obj5Dynclass);

    JSObject::SetPrototype(thread_, obj2, JSHandle<JSTaggedValue>(obj3));

    JSHandle<JSHClass> obj1Dynclass(thread_, obj1->GetJSHClass());
    JSHandle<JSHClass> obj2Dynclass(thread_, obj2->GetJSHClass());
    JSHandle<JSHClass> obj3Dynclass(thread_, obj3->GetJSHClass());
    JSHandle<JSHClass> obj4Dynclass(thread_, obj4->GetJSHClass());
    JSHandle<JSHClass> obj6Dynclass(thread_, obj6->GetJSHClass());

    JSTaggedValue obj6Marker = obj6Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(obj6Marker.IsProtoChangeMarker());
    bool hasChanged6 = ProtoChangeMarker::Cast(obj6Marker.GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(hasChanged6);

    JSTaggedValue obj4Marker = obj4Dynclass->GetProtoChangeMarker();
    EXPECT_TRUE(obj4Marker.IsProtoChangeMarker());
    bool hasChanged4 = ProtoChangeMarker::Cast(obj4Marker.GetTaggedObject())->GetHasChanged();
    EXPECT_TRUE(hasChanged4);

    JSTaggedValue protoDetails1 = obj1Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails1.IsProtoChangeDetails());
    JSTaggedValue protoDetails2 = obj2Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails2.IsProtoChangeDetails());
    JSTaggedValue protoDetails3 = obj3Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails3.IsProtoChangeDetails());
    JSTaggedValue protoDetails4 = obj4Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails4.IsProtoChangeDetails());
    JSTaggedValue protoDetails6 = obj6Dynclass->GetProtoChangeDetails();
    EXPECT_TRUE(protoDetails6.IsProtoChangeDetails());

    JSTaggedValue listeners1 = ProtoChangeDetails::Cast(protoDetails1.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners1 != JSTaggedValue(0));
    JSTaggedValue listeners2 = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners2 != JSTaggedValue(0));
    JSTaggedValue listeners3 = ProtoChangeDetails::Cast(protoDetails3.GetTaggedObject())->GetChangeListener();
    EXPECT_TRUE(listeners3 != JSTaggedValue(0));

    JSTaggedValue index2 = ProtoChangeDetails::Cast(protoDetails2.GetTaggedObject())->GetRegisterIndex();
    JSTaggedValue index3 = ProtoChangeDetails::Cast(protoDetails3.GetTaggedObject())->GetRegisterIndex();
    JSTaggedValue index4 = ProtoChangeDetails::Cast(protoDetails4.GetTaggedObject())->GetRegisterIndex();
    JSTaggedValue index6 = ProtoChangeDetails::Cast(protoDetails6.GetTaggedObject())->GetRegisterIndex();

    JSTaggedValue result2 = ChangeListener::Cast(listeners3.GetTaggedObject())->Get(index2.GetArrayLength());
    JSTaggedValue result3 = ChangeListener::Cast(listeners1.GetTaggedObject())->Get(index3.GetArrayLength());
    JSTaggedValue result4 = ChangeListener::Cast(listeners2.GetTaggedObject())->Get(index4.GetArrayLength());
    JSTaggedValue result6 = ChangeListener::Cast(listeners2.GetTaggedObject())->Get(index6.GetArrayLength());

    EXPECT_TRUE(result2 == obj2Dynclass.GetTaggedValue());
    EXPECT_TRUE(result3 == obj3Dynclass.GetTaggedValue());
    EXPECT_TRUE(result4 == obj4Dynclass.GetTaggedValue());
    EXPECT_TRUE(result6 == obj6Dynclass.GetTaggedValue());
}

TEST_F(JSObjectTest, NativePointerField)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> objFunc(thread_, JSObjectTestCreate(thread_));
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFunc), objFunc);
    obj->SetHash(87);
    EXPECT_TRUE(obj->GetHash() == 87);

    obj->SetNativePointerFieldCount(1);
    char array[] = "Hello World!";
    obj->SetNativePointerField(0, array, nullptr, nullptr);
    int32_t count = obj->GetNativePointerFieldCount();
    EXPECT_TRUE(count == 1);
    void *pointer = obj->GetNativePointerField(0);
    EXPECT_TRUE(pointer == array);
}

// NOLINTEND(modernize-avoid-c-arrays,readability-magic-numbers)

}  // namespace ark::test
